package hist_data;

import iblink.core.HistoricalData;
import iblink.core.Stock;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import dal.IHistoricalDataAccessor;

import util.DateUtil;

public class HistoricalDataCache {

	private IHistoricalDataAccessor histDataAccess;

	private Map<Stock, Map<Date, List<MinuteBar>>> minuteBars = new HashMap<Stock, Map<Date, List<MinuteBar>>>();
	private Map<Stock, Map<Date, DailyBar>> dayBars = new HashMap<Stock, Map<Date, DailyBar>>();

	private Map<Date, Map<Stock, Map<Stock, DailyPair>>> dailyPairs = new HashMap<Date, Map<Stock, Map<Stock, DailyPair>>>();

	public HistoricalDataCache(IHistoricalDataAccessor histDataAccess) {
		this.histDataAccess = histDataAccess;
	}

	public DailyPair getDailyPair(Stock stock1, Stock stock2, Date d) {

		if (dailyPairs.containsKey(d) && dailyPairs.get(d).containsKey(stock1)
				&& dailyPairs.get(d).get(stock1).containsKey(stock2)) {
			return dailyPairs.get(d).get(stock1).get(stock2);
		}

		List<MinuteBar> minuteBars1 = getMinuteBars(stock1, d);
		List<MinuteBar> minuteBars2 = getMinuteBars(stock2, d);
		DailyPair dp = DailyPair.createDailyPair(d, stock1, stock2,
				minuteBars1, minuteBars2);

		if (!dailyPairs.containsKey(d))
			dailyPairs.put(d, new HashMap<Stock, Map<Stock, DailyPair>>());

		if (!dailyPairs.get(d).containsKey(stock1)) {
			dailyPairs.get(d).put(stock1, new HashMap<Stock, DailyPair>());
		}

		if (!dailyPairs.get(d).containsKey(stock2))
			dailyPairs.get(d).put(stock2, new HashMap<Stock, DailyPair>());

		dailyPairs.get(d).get(stock1).put(stock2, dp);
		dailyPairs.get(d).get(stock2).put(stock1, dp);

		histDataAccess.saveDailyPair(dp);

		return dp;
	}

	public List<MinuteBar> getMinuteBars(Stock s, Date d) {

		if (!minuteBars.containsKey(s))
			minuteBars.put(s, new HashMap<Date, List<MinuteBar>>());

		if (!minuteBars.get(s).containsKey(d)) {
			List<MinuteBar> mbList = histDataAccess.getMinuteBars(s, d);
			mbList = inflateBars(s, d, mbList);
			minuteBars.get(s).put(d, mbList);
		}

		return minuteBars.get(s).get(d);
	}

	private List<MinuteBar> inflateBars(Stock stock, Date tradeDate,
			List<MinuteBar> bars) {

		List<MinuteBar> completeBars = new ArrayList<MinuteBar>();

		Date marketOpen = DateUtil.resetTime(tradeDate, 9, 30, 0, 0);
		Date marketClose = DateUtil.resetTime(tradeDate, 16, 0, 0, 0);
		Date nextBarTime = marketOpen;

		Date prevTradeDate = DateUtil.adjustTradeDate(tradeDate, -1);
		MinuteBar prevDayLastBar = histDataAccess.getLastBar(stock,
				prevTradeDate);

		MinuteBar prevBar = prevDayLastBar;

		for (MinuteBar hd : bars) {

			if (!prevBar.getDate().before(hd.getDate()))
				throw new RuntimeException("bars not in chrnological order");

			while (nextBarTime.before(hd.getDate())) {
				completeBars.add(new MinuteBar(nextBarTime, prevBar.getClose(),
						prevBar.getClose(), prevBar.getClose(), prevBar
								.getClose(), 0, 0, prevBar.getClose()));
				prevBar = completeBars.get(completeBars.size() - 1);
				nextBarTime = DateUtil.adjustMinutes(nextBarTime, 1);
			}

			if (!nextBarTime.equals(hd.getDate()))
				throw new RuntimeException("Expecting next bar time ("
						+ nextBarTime + ") to equal " + "(" + hd.getDate()
						+ ")");

			completeBars.add(new MinuteBar(nextBarTime, hd.getOpen(), hd
					.getHigh(), hd.getLow(), hd.getClose(), hd.getVolume(), hd
					.getCount(), hd.getWAP()));

			prevBar = completeBars.get(completeBars.size() - 1);
			nextBarTime = DateUtil.adjustMinutes(nextBarTime, 1);
		}

		while (nextBarTime.before(marketClose)) {
			completeBars.add(new MinuteBar(nextBarTime, prevBar.getOpen(),
					prevBar.getHigh(), prevBar.getLow(), prevBar.getClose(), 0,
					0, prevBar.getWAP()));
			nextBarTime = DateUtil.adjustMinutes(nextBarTime, 1);
		}

		if (completeBars.size() != 390)
			throw new RuntimeException("expecting 390 bars, got "
					+ completeBars.size());

		return completeBars;
	}

	private List<MinuteBar> convertBars(List<HistoricalData> bars) {

		List<MinuteBar> convertedBars = new ArrayList<MinuteBar>();

		for (HistoricalData hd : bars) {
			convertedBars.add(new MinuteBar(hd));
		}

		return convertedBars;
	}

	public void addBars(Stock stock, Date tradeDate, List<HistoricalData> bars) {

		Date date = DateUtil.resetTime(tradeDate);

		List<MinuteBar> convertedBars = convertBars(bars);
		histDataAccess.saveMinuteBars(stock, convertedBars);

		List<MinuteBar> inflatedBars = inflateBars(stock, date, convertedBars);

		double dayOpen = inflatedBars.get(0).getOpen();
		double dayClose = inflatedBars.get(inflatedBars.size() - 1).getClose();
		double dayLow = Double.MAX_VALUE;
		double dayHigh = Double.MIN_VALUE;
		double wapSum = 0;
		int volumeSum = 0;
		int countSum = 0;
		int activityCount = 0;

		// TODO: check to see if date already exists, throw exception
		minuteBars.put(stock, new HashMap<Date, List<MinuteBar>>());
		minuteBars.get(stock).put(date, inflatedBars);

		List<MinuteBar> lastBars = new LinkedList<MinuteBar>();

		for (MinuteBar mb : inflatedBars) {

			// //////////
			// minute bar stats
			// //////////

			double sodChange = mb.getClose() / dayOpen - 1;

			Double oneBarChange = null;
			Double fiveBarChange = null;
			Double tenBarChange = null;

			if (!lastBars.isEmpty()) {
				oneBarChange = mb.getClose() / lastBars.get(0).getClose() - 1;
			}

			double fiveBarSum = 0;
			if (lastBars.size() >= 5) {
				for (int i = 0; i < 5; i++)
					fiveBarSum += mb.getClose() / lastBars.get(i).getClose()
							- 1;
				fiveBarChange = fiveBarSum / 5;
			}

			double tenBarSum = fiveBarSum;
			if (lastBars.size() >= 10) {
				for (int i = 5; i < 10; i++)
					tenBarSum += mb.getClose() / lastBars.get(i).getClose() - 1;
				fiveBarChange = tenBarSum / 10;
			}

			lastBars.add(0, mb);

			if (lastBars.size() > 10)
				lastBars.remove(lastBars.size() - 1);

			mb.setComputedFields(sodChange, oneBarChange, fiveBarChange,
					tenBarChange);

			// //////////
			// daily stats below
			// /////////

			wapSum += mb.getWAP();
			countSum += mb.getCount();
			volumeSum += mb.getVolume();
			activityCount += mb.getVolume() > 0 ? 1 : 0;

			if (mb.getLow() < dayLow)
				dayLow = mb.getLow();

			if (mb.getHigh() > dayHigh)
				dayHigh = mb.getHigh();
		}

		double activityPercent = activityCount / 390d;

		if (activityCount != convertedBars.size())
			throw new RuntimeException(
					"Expecting activity count to equal deflated bars size");

		DailyBar dailyBar = new DailyBar(date, dayOpen, dayHigh, dayLow,
				dayClose, volumeSum, countSum, activityPercent);
		// TODO: check to see if date already exists, throw exception
		dayBars.put(stock, new HashMap<Date, DailyBar>());
		dayBars.get(stock).put(date, dailyBar);

		histDataAccess.saveDailyBar(stock, dayBars.get(stock).get(date));
	}
}